/*
 * ============================================================================
 *
 *       Filename:  serial.c
 *
 *    Description:  
 *
 *        Version:  1.0
 *        Created:  2020年06月08日 09时20分43秒
 *       Revision:  none
 *       Compiler:  gcc
 *
 *         Author:  zkk(zkk), zkk@126.com
 *   Organization:  
 *
 * ============================================================================
 */
#include "config.h"
#include <stdio.h>
#include <stdint.h>
#include <termios.h>  
#include <fcntl.h>  
#include <arpa/inet.h>
#include <ev.h>

#include "../include/log.h"
#include "../include/arg.h"
#include "../include/list.h"
#include "../include/serial.h"
#include "../include/common.h"

static int set_opt(int fd, int nSpeed, int nBits, char nEvent, int nStop,
	uint8_t vmin, uint8_t vtime)  
{ 
	struct termios newtio,oldtio;  
	if  ( tcgetattr( fd,&oldtio)  !=  0) {   
		sys_err("SetupSerial 1");  
		return -1;  
	}  
	bzero( &newtio, sizeof( newtio ) );  
	/* canon */
	newtio.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP
		|INLCR|IGNCR|ICRNL|IXON);
	newtio.c_oflag &= ~OPOST;
	newtio.c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN);
	newtio.c_cflag &= ~(CSIZE|PARENB);
	newtio.c_cflag |= CS8;

	newtio.c_cflag  |=  CLOCAL | CREAD;  
	newtio.c_cflag &= ~CSIZE;  

	switch( nBits )  
	{  
	case 5:
		newtio.c_cflag |= CS5;  
		break;  
	case 6:
		newtio.c_cflag |= CS6;  
		break;  
	case 7:  
		newtio.c_cflag |= CS7;  
		break;  
	case 8:  
		newtio.c_cflag |= CS8;  
		break;  
	}  

	switch( nEvent )  
	{  
	case 'O':  
		newtio.c_cflag |= PARENB;  
		newtio.c_cflag |= PARODD;  
		newtio.c_iflag |= (INPCK | ISTRIP);  
		break;  
	case 'E':   
		newtio.c_iflag |= (INPCK | ISTRIP);  
		newtio.c_cflag |= PARENB;  
		newtio.c_cflag &= ~PARODD;  
		break;  
	case 'N':    
		newtio.c_cflag &= ~PARENB;  
		break;  
	default:
		sys_err("error parity\n");
		exit(-1);
	}  

	switch( nSpeed )  
	{  
	case 2400:  
		cfsetispeed(&newtio, B2400);  
		cfsetospeed(&newtio, B2400);  
		break;  
	case 4800:  
		cfsetispeed(&newtio, B4800);  
		cfsetospeed(&newtio, B4800);  
		break;  
	case 9600:  
		cfsetispeed(&newtio, B9600);  
		cfsetospeed(&newtio, B9600);  
		break;  
	case 19200:  
		cfsetispeed(&newtio, B19200);  
		cfsetospeed(&newtio, B19200);  
		break;  
	case 38400:
		cfsetispeed(&newtio, B38400);  
		cfsetospeed(&newtio, B38400);  
		break;  
	case 57600:
		cfsetispeed(&newtio, B57600);  
		cfsetospeed(&newtio, B57600);  
		break;  
	case 115200:  
		cfsetispeed(&newtio, B115200);  
		cfsetospeed(&newtio, B115200);  
		break;  
	case 230400:  
		cfsetispeed(&newtio, B230400);  
		cfsetospeed(&newtio, B230400);  
		break;  
	case 460800:  
		cfsetispeed(&newtio, B460800);  
		cfsetospeed(&newtio, B460800);  
		break;  
	default:  
		sys_err("error bps\n");
		exit(-1);
	}  
	if( nStop == 1 )  
		newtio.c_cflag &=  ~CSTOPB;  
	else if ( nStop == 2 )  
		newtio.c_cflag |=  CSTOPB;  
	newtio.c_cc[VTIME]  = vtime;
	newtio.c_cc[VMIN] = vmin;
	tcflush(fd,TCIFLUSH);  
	if((tcsetattr(fd,TCSANOW,&newtio))!=0)  
	{  
		sys_err("com set error");  
		return -1;  
	}  
	return 0;  
}

static void free_serial(serial_t *serial)
{
	sys_err("serial error, close and free serial\n");
	bfree(serial->rxbuf);
	bfree(serial->txbuf);
	ss_free(serial->rxbuf);
	ss_free(serial->txbuf);
	ss_free(serial->recv_ctx);
	ss_free(serial->send_ctx);
	ss_free(serial);
}

void close_and_free_serial(EV_P_ serial_t *serial)
{
	if (serial != NULL) {
		sys_err("serial error, free serial fd: %d\n", serial->fd);
		ev_io_stop(EV_A_ & serial->send_ctx->io);
		ev_io_stop(EV_A_ & serial->recv_ctx->io);
		close(serial->fd);
		free_serial(serial);
		ev_break (EV_A_ EVBREAK_ALL);
	}
}

void serial_recv_cb(EV_P_ ev_io *w, int revents)
{
	serial_ctx_t *serial_recv_ctx   = container_of(w, serial_ctx_t, io);
	serial_t *serial		= serial_recv_ctx->serial;

	int i = 0;
	uint8_t crc = 0;
	ssize_t r;
	char buffer[MSS] = {0};

	r = read(serial->fd, buffer, sizeof(buffer));
	if (r == 0) {
		return;
	} else if (r == -1) {
		if (errno == EAGAIN || errno == EWOULDBLOCK) {
			/* continue to wait for recv */
			sys_debug("recv again\n");
			return;
		} else {
			sys_err("recv failed: %s(%d)\n", strerror(errno), errno);
			close_and_free_serial(EV_A_ serial);
		}
	} else {
		bconcat(serial->rxbuf, buffer, r);

		/* debug  */
		int j = 0;
		printf("serial->rxbuf:");
		for(j = 0; j < serial->rxbuf->len; j++) {
			printf("%02x", serial->rxbuf->array[j]);
		}
		printf("\n");

		i = 0;
		while(serial->rxbuf->len) {
			if((serial->rxbuf->array[0] & 0xff) != 0xaa)
				bshrink(serial->rxbuf, 1);
			else if(serial->rxbuf->array[1] == 0x11) {
				breset(serial->rxbuf);
				return;
			} else
				break;
		}
		printf("is 0xaa %02x\n", serial->rxbuf->array[0]);

		if(serial->rxbuf->len < serial->rxbuf->array[3] + 4) {
			printf("data not enght, waiting data\n");
			return;
		}

		bzero(buffer, MSS);

		printf("serial->rxbuf:");
		for(i = 0, r = 0; i < serial->rxbuf->array[3] + 4; i++) {
			printf("%02x ", serial->rxbuf->array[i]);
			sprintf(&buffer[r], "%02x", (uint8_t )serial->rxbuf->array[i]);
			crc += serial->rxbuf->array[i];
			r += 2;
		}
		sprintf(&buffer[r], "%02x", (uint8_t)serial->rxbuf->array[i]);
		printf("%02x\n", serial->rxbuf->array[i]);
		printf("publish buffer: %s\n", buffer);

		if(crc == (serial->rxbuf->array[i] & 0xff)) {
			int ret  = mosquitto_publish(serial->mymq->mosq, NULL,
				args_info.pubtopic_arg, r + 2, buffer, 0, 0);
			if(ret != MOSQ_ERR_SUCCESS) {
				sys_err("mqtt publish failed and close mqtt free serial\n");
				close_and_free_serial(EV_A_ serial);
				return;
			}
		} else {
			sys_debug("crc err : crc %02x, array = %02x\n",
				crc, serial->rxbuf->array[i]);
		}

		bshrink(serial->rxbuf, i + 1);
	}
}

static void serial_send_cb(EV_P_ ev_io *w, int revents)
{
	serial_ctx_t *serial_send_ctx = container_of(w, serial_ctx_t, io);
	serial_t *serial	      = serial_send_ctx->serial;

	if (bufempty(serial->txbuf)) {
		return;
	} else {
		ssize_t s = write(serial->fd, serial->txbuf->array + serial->txbuf->idx,
			serial->txbuf->len);
		sys_debug("write to serial %u bytes\n", s);
		if (s == -1) {
			if (errno != EAGAIN && errno != EWOULDBLOCK) {
				sys_err("serial_send_cb error\n");
				close_and_free_serial(EV_A_ serial);
				sys_err("ttl: %s error, exit\n", args_info.dev_arg);
				exit(-1);
			}
			return;
		} else if (s < (ssize_t)(serial->txbuf->len)) {
			bshrink(serial->txbuf, s);
			return;
		} else {
			breset(serial->txbuf);
		}
	}

	return;
}

static serial_t *new_serial(int fd)
{
	serial_t *serial;
	serial = ss_malloc(sizeof(serial_t));

	serial->fd = fd;
	serial->rxbuf      = ss_malloc(sizeof(buffer_t));
	serial->txbuf      = ss_malloc(sizeof(buffer_t));
	serial->recv_ctx		= ss_malloc(sizeof(serial_ctx_t));
	serial->send_ctx		= ss_malloc(sizeof(serial_ctx_t));
	serial->recv_ctx->serial	= serial;
	serial->send_ctx->serial	= serial;
	balloc(serial->rxbuf, BUF_SIZE);
	balloc(serial->txbuf, BUF_SIZE);

	ev_io_init(&serial->recv_ctx->io, serial_recv_cb, fd, EV_READ);
	ev_io_init(&serial->send_ctx->io, serial_send_cb, fd, EV_WRITE);

	return serial;
}

serial_t *create_serial()
{
	int fd, ret;
	fd = open(args_info.dev_arg, O_RDWR | O_NOCTTY);
	if(fd < 0) {
		fprintf(stderr, "open %s failed:%s\n", 
			args_info.dev_arg, 
			strerror(errno));
		exit(-1);
	}

	ret = set_opt(fd , args_info.speed_arg, args_info.data_arg, 
		args_info.parity_arg[0], args_info.stop_arg,
		args_info.vmin_arg, args_info.vtime_arg);
	if(ret < 0) {
		fprintf(stderr, "set tty failed:%s\n", 
			strerror(errno));
		exit(-1);
	}
	serial_t * serial = new_serial(fd);

	return serial;
}
